using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.IO;
using System.Management;

namespace Lizk.Utilities
{
    /// <summary>
    /// Eventhandler for device drive changes.
    /// </summary>
    /// <param name="sender">Sender of the event.</param>
    /// <param name="volume">Info about a drive whose status has changed.</param>
    public delegate void DeviceChangedEventHandler(object sender, DriveInfo volume);
    /// <summary>
    /// Monitors available local drives for changes (Registers when a usb drive is connected fx.)
    /// </summary>
    public class DiskMonitor : IDisposable
    {
        /// <summary>
        /// Event on removal of local disk volume
        /// </summary>
        public event DeviceChangedEventHandler DiskRemoved;
        
        /// <summary>
        /// Event on connection of local disk volume
        /// </summary>
        public event DeviceChangedEventHandler DiskFound;

        private Dictionary<string, DriveInfo> volumes = new Dictionary<string, DriveInfo>();
        private Thread slave;
        private AutoResetEvent are = new AutoResetEvent(false);
        private bool loaded = false, kill = false, hooked = false;
        private DriveType[] acceptedTypes = new DriveType[] { DriveType.Network, DriveType.Fixed, DriveType.CDRom };

        /// <summary>
        /// Creates a new Disk monitor. Hooks Application.Application exit to dispose on program exit.
        /// </summary>
        public DiskMonitor(params DriveType[] types)
        {
            if (types.Length != 0)
                acceptedTypes = types;

            DisksUpdated();
            Application.ApplicationExit += delegate(object sender, EventArgs ea)
            {
                Dispose();
            };

            if (Array.IndexOf(acceptedTypes, DriveType.Network) > -1)
            {
                System.Net.NetworkInformation.NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;
                System.Net.NetworkInformation.NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged;
            }
        }

        void NetworkChange_NetworkAvailabilityChanged(object sender, System.Net.NetworkInformation.NetworkAvailabilityEventArgs e)
        {
            if(!e.IsAvailable && !kill)
                DisksUpdated();
        }

        void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
        {
            if (!kill)
            {
                DisksUpdated();
            }
        }



        /// <summary>
        /// Disposes object. Kills worker thread and removes the messagefilter. After being disposed, this object cannot be used again. Object disposes itself upon application exit.
        /// </summary>
        public void Dispose()
        {
            
            kill = true;
            if (slave != null && slave.ThreadState == ThreadState.WaitSleepJoin)
            {
                are.Set();
            }
            if (Array.IndexOf(acceptedTypes, DriveType.Network) > -1)
            {
                System.Net.NetworkInformation.NetworkChange.NetworkAddressChanged -= NetworkChange_NetworkAddressChanged;
                System.Net.NetworkInformation.NetworkChange.NetworkAvailabilityChanged -= NetworkChange_NetworkAvailabilityChanged;
            }
        }
        /// <summary>
        /// Disposes the object on deconstruction.
        /// </summary>
        ~DiskMonitor()
        {
            Dispose();
        }

        private void DisksUpdated()
        {
            if (kill)
                return;

            if (slave == null || slave.ThreadState == ThreadState.Stopped)
            {
                slave = new Thread(new ThreadStart(FindDisks));
                slave.IsBackground = true;
                slave.Start();
            }
            else
            {
                are.Set();
            }
        }

        private void FindDisks()
        {
            while (!kill)
            {
                string[] hallo = Environment.GetLogicalDrives();
                List<string> now = new List<string>();
                now.AddRange(volumes.Keys);


                foreach (string s in hallo)
                {
                    DriveInfo di = new DriveInfo(s);

                    if (Array.IndexOf(acceptedTypes, di.DriveType) == -1)
                    {
                        continue;
                    }

                    if (di.DriveType == DriveType.Network && !Lizk.Utilities.Net.NetworkDrive.IsNetworkDriveOnline(di.RootDirectory.FullName, 1000))
                        continue;

                    if (!di.IsReady)
                        continue;

                    if (!volumes.ContainsKey(s))
                    {

                        volumes.Add(s, di);
                        if (DiskFound != null && loaded && !kill)
                        {
                            DiskFound(this, di);
                        }
                    }
                    now.Remove(s);
                }

                foreach (string s in now)
                {
                    if (DiskRemoved != null && loaded && !kill)
                    {
                        DiskRemoved(this, volumes[s]);
                    }
                    volumes.Remove(s);
                }

                if (!loaded) loaded = true;
                are.WaitOne();
            }
        }

        #region IMessageFilter Members
        /// <summary>
        /// Device Change
        /// </summary>
        public static  int WM_DEVICECHANGE = 0x0219;
        /// <summary>
        /// MessageFilter method
        /// </summary>
        /// <param name="m">Windows Message</param>
        /// <returns>false (meaning that it does not block for further parsing of windows messages)</returns>
        public bool PreFilterMessage(ref Message m)
        {
           if (m.Msg == WM_DEVICECHANGE)
            {
                DisksUpdated();
            }
            return false;
        }

        #endregion
    }
}
